﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using WPFEdit.Util;

namespace WPFEdit.Document.Factory
{
    using TEditorInfo = Tuple<Type,ConstructorInfo,bool>;
    public class DocEditorFactory : InterfaceFactory<IDocumentEditor, Type, TEditorInfo, DocumentEditorAttribute>
    {
        protected override void AddType(Type type, object[] attributeInstances, ConstructorInfo constructorInfo)
        {
            foreach (DocumentEditorAttribute editorInfo in attributeInstances)
            {
                m_factoryInstances.Add(editorInfo.DocumentDataType, new TEditorInfo(type, constructorInfo, editorInfo.EditsSubclasses));
            }
        }

        public IDocumentEditor GetDocumentEditor(Type type)
        {
            TEditorInfo editor;
            if (m_factoryInstances.TryGetValue(type, out editor))
            {
                return (IDocumentEditor)editor.Item2.Invoke(Reflection.EmptyObjects);
            }

			KeyValuePair<Type, TEditorInfo> keyVal = TryGetBestMatch(type); 
            if (keyVal.Value != null)
            {
                editor = keyVal.Value;
                return (IDocumentEditor)editor.Item2.Invoke(Reflection.EmptyObjects);
            }
            return null;
        }

		private KeyValuePair<Type, TEditorInfo> TryGetBestMatch(Type type)
		{
			var baseClassOnlyMatches = m_factoryInstances.Where<KeyValuePair<Type, TEditorInfo>>(pair => (pair.Value.Item3 == true && pair.Key.IsAssignableFrom(type) && !pair.Key.IsInterface));
			KeyValuePair<Type, TEditorInfo> bestMatch = new KeyValuePair<Type,TEditorInfo>();
			Type cur;
			int separation,bestSeparation = int.MaxValue;
			foreach (KeyValuePair<Type, TEditorInfo> match in baseClassOnlyMatches)
			{
				//Count the separation from the target type
				cur = type;
				separation = 0;
				while (!cur.Equals(match.Key) && !cur.Equals(typeof(object)) && separation < bestSeparation)
				{
					separation++;
					cur = cur.BaseType;
				}
				if (separation < bestSeparation)
				{
					if (separation == 1) return match;//direct base class, automatic match.
					bestMatch = match;
					bestSeparation = separation;
				}
			}
			if (bestMatch.Value != null) return bestMatch;

			return m_factoryInstances.FirstOrDefault<KeyValuePair<Type, TEditorInfo>>(pair => (pair.Value.Item3 == true && pair.Key.IsAssignableFrom(type) && pair.Key.IsInterface));
		}
    }
}
